jqkit.math = {};

/**
 * Rounds a numeric value to a given precision. If not precision is given, rounds to integer.
 * @param {number} value The value to round.
 * @param {number} opt_precision The desired precision.
 * @return {number} Rounded number.
 */
jqkit.math.round = function(value, opt_precision) {
	opt_precision = opt_precision || 0;
	if (opt_precision === 0) {
		return Math.round(value);
	}
	var factor = Math.pow(10, opt_precision);
	return Math.round(value * factor) / factor;
};

/**
 * A number range.
 * @param {number} a One end of the range.
 * @param {number} b The other end of the range.
 * @constructor
 */
jqkit.math.Range = function(a, b) {
	this.start = a < b ? a : b;
	this.end = a < b ? b : a;
};

/**
 * @return {!goog.math.Range} A clone of this Range.
 */
jqkit.math.Range.prototype.clone = function() {
  return new jqkit.math.Range(this.start, this.end);
};

/**
 * Returns a string representing the range.
 * @return {string} In the form [-3.5, 8.13].
 */
jqkit.math.Range.prototype.toString = function() {
	return '[' + this.start + ', ' + this.end + ']';
};

/**
 * Returns a the size of the range.
 * @return {number} The number of indexes in the range. All ranges will have a size greater than 0.
 */
jqkit.math.Range.prototype.size = function() {
	var size = Math.abs(this.end - this.start);
	return size > 0 ? size : 1;
};

/**
 * Compares ranges for equality.
 * @param {jqkit.math.Range} a A Range.
 * @param {jqkit.math.Range} b A Range.
 * @return {boolean} True if both the starts and the ends of the ranges are equal, or if both ranges are null.
 */
jqkit.math.Range.equals = function(a, b) {
	if (a === b) {
		return true;
	}
	if (!a || !b) {
		return false;
	}
	return a.start === b.start && a.end === b.end;
};

/**
 * Compares ranges for equality.
 * @param {jqkit.math.Range} otherRange A Range.
 * @return {boolean} True if both the starts and the ends of the ranges are equal, or if both ranges are null.
 */
jqkit.math.Range.prototype.equals = function(otherRange) {
	return jqkit.math.Range.equals(this, otherRange);
};

/**
 * Given two ranges on the same dimension, this method returns the intersection of those ranges.
 * @param {jqkit.math.Range} a A Range.
 * @param {jqkit.math.Range} b A Range.
 * @return {jqkit.math.Range} A new Range representing the intersection of two ranges, or null if there is no intersection. Ranges are assumed to
 * include their end points, and the intersection can be a point.
 */
jqkit.math.Range.intersection = function(a, b) {
	var c0 = Math.max(a.start, b.start);
	var c1 = Math.min(a.end, b.end);
	return (c0 <= c1) ? new jqkit.math.Range(c0, c1) : null;
};

/**
 * Given two ranges on the same dimension, determines whether they intersect.
 * @param {jqkit.math.Range} a A Range.
 * @param {jqkit.math.Range} b A Range.
 * @return {boolean} Whether they intersect.
 */
jqkit.math.Range.hasIntersection = function(a, b) {
	return Math.max(a.start, b.start) <= Math.min(a.end, b.end);
};

/**
 * Given two ranges on the same dimension, this method returns the difference of those ranges. Will prefer the left side of the range if range a contains range b.
 * @param {jqkit.math.Range} a A Range.
 * @param {jqkit.math.Range} b A Range.
 * @return {jqkit.math.Range} A new Range representing the difference of two ranges, or null if there is no difference or range b contains range a. Ranges are
 * assumed to include their end points, and the difference can be a point.
 */
jqkit.math.Range.subtract = function(a, b) {
	if (a.equals(b) === true || b.contains(a) === true) {
		return null;
	}
	
	if (jqkit.math.Range.hasIntersection(a, b) === false) {
		return a.clone();
	}
	
	var c0 = a.start;
	var c1 = a.end;
	
	if (a.contains(b) === true && a.start !== b.start && a.end !== b.end) {
		c1 = b.start - 1;
	} else if (a.start >= b.start) {
		c0 = b.end + 1;
	} else {
		c1 = b.start - 1;
	}
	
	return new jqkit.math.Range(c0, c1);
};

/**
 * Given two ranges on the same dimension, this returns a range that covers both ranges.
 * @param {jqkit.math.Range} a A Range.
 * @param {jqkit.math.Range} b A Range.
 * @return {!jqkit.math.Range} A new Range representing the bounding range.
 */
jqkit.math.Range.boundingRange = function(a, b) {
	return new jqkit.math.Range(Math.min(a.start, b.start), Math.max(a.end, b.end));
};

/**
 * Given two ranges, returns true if the first range completely overlaps the second.
 * @param {jqkit.math.Range} a The first Range.
 * @param {jqkit.math.Range} b The second Range.
 * @return {boolean} True if b is contained inside a, false otherwise.
 */
jqkit.math.Range.contains = function(a, b) {
	return a.start <= b.start && a.end >= b.end;
};

/**
 * Returns true if the range completely overlaps the given additional range.
 * @param {jqkit.math.Range} a The additional range.
 * @return {boolean} True if a is contained inside of the range instance, false otherwise.
 */
jqkit.math.Range.prototype.contains = function(a) {
	return jqkit.math.Range.contains(this, a);
};

/**
 * Given a range and a point, returns true if the range contains the point.
 * @param {jqkit.math.Range} range The range.
 * @param {number} point The point.
 * @return {boolean} True if point is contained inside range, false otherwise.
 */
jqkit.math.Range.containsPoint = function(range, point) {
	return range.start <= point && range.end >= point;
};

/**
 * Returns true if the range contains the given point.
 * @param {number} point The point.
 * @return {boolean} True if point is contained inside range, false otherwise.
 */
jqkit.math.Range.prototype.containsPoint = function(point) {
	return jqkit.math.Range.containsPoint(this, point);
};

/**
 * Class for representing sizes consisting of a width and height. Undefined width and height support is deprecated 
 * and results in compiler warning.
 * @param {number} width Width.
 * @param {number} height Height.
 * @constructor
 */
jqkit.math.Size = function(width, height) {
	this.width = width;
	this.height = height;
};

/**
 * Compares sizes for equality.
 * @param {jqkit.math.Size} a A Size.
 * @param {jqkit.math.Size} b A Size.
 * @return {boolean} True if the sizes have equal widths and equal heights, or if both are null.
 */
jqkit.math.Size.equals = function(a, b) {
	if (a === b) {
		return true;
	}
	if (!a || !b) {
		return false;
	}
	return a.width === b.width && a.height === b.height;
};

/**
 * Compares sizes for equality.
 * @param {jqkit.math.Size} otherSize A Size.
 * @return {boolean} True if the sizes have equal widths and equal heights, or if both are null.
 */
jqkit.math.Size.prototype.equals = function(otherSize) {
	return jqkit.math.Size.equals(this, otherSize);
};

/**
 * @return {jqkit.math.Size} A new copy of the Size.
 */
jqkit.math.Size.prototype.clone = function() {
  return new jqkit.math.Size(this.width, this.height);
};

/**
 * Returns a nice string representing size.
 * @return {string} In the form (50 x 73).
 */
jqkit.math.Size.prototype.toString = function() {
	return '(' + this.width + ' x ' + this.height + ')';
};

/**
 * @return {number} The area of the size (width * height).
 */
jqkit.math.Size.prototype.area = function() {
	return this.width * this.height;
};

/**
 * @return {number} The perimeter of the size (width + height) * 2.
 */
jqkit.math.Size.prototype.perimeter = function() {
	return (this.width + this.height) * 2;
};

/**
 * @return {number} The ratio of the size's width to its height.
 */
jqkit.math.Size.prototype.aspectRatio = function() {
	return this.width / this.height;
};

/**
 * Scales the size uniformly by a factor.
 * @param {number} factor The scale factor.
 * @return {!jqkit.math.Size} This Size object after scaling.
 */
jqkit.math.Size.prototype.scale = function(factor) {
	this.width *= factor;
	this.height *= factor;
	return this;
};

/**
 * Uniformly scales the size to fit inside the dimensions of a given size. The original aspect ratio will be preserved.
 * This function assumes that both Sizes contain strictly positive dimensions.
 * @param {!jqkit.math.Size} target The target size.
 * @return {!jqkit.math.Size} This Size object, after optional scaling.
 */
jqkit.math.Size.prototype.scaleToFit = function(target) {
	var factor = (this.aspectRatio() > target.aspectRatio()) ? (target.width / this.width) : (target.height / this.height);
	return this.scale(factor);
};